package com.hoioy.clazz.spellearn;

import com.hoioy.clazz.spellearn.model.Inventor;
import com.hoioy.clazz.spellearn.model.PlaceOfBirth;
import com.hoioy.clazz.spellearn.model.Society;
import org.springframework.expression.*;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.math.RoundingMode;
import java.util.*;

public class SpELReference {
    public static void main(String[] args) throws NoSuchMethodException {
        //我将SpEL的表达式语法分为5类，依次进行讲解

//        //基础语法
//        one();
//        //运算符语法
//        two();
//        //操作对象和属性的语法
//        three();
//        //操作集合的语法
//        four();
//        //表达式模板语法
        five();
    }

    //基础
    public static void one() {
        ExpressionParser parser = new SpelExpressionParser();
        //文字表达式
        //字符串是单引号
        System.out.println(parser.parseExpression("'Hello World'").getValue(String.class));
        System.out.println(parser.parseExpression("10").getValue(Integer.class));
        System.out.println(parser.parseExpression("6.0221415E+23").getValue(Double.class));
        System.out.println(parser.parseExpression("0x7FFFFFFF").getValue(Integer.class));
        System.out.println(parser.parseExpression("true").getValue(Boolean.class));
        System.out.println(parser.parseExpression("null").getValue(Object.class));

        //参考官网，使用官网中的Java Bean
        // https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#expressions-example-classes
        // Inventor等类属性解释
        // 构建模型对象，方便读取
        Inventor hai = new Inventor("海先生", new Date(), "中国");
        hai.setPlaceOfBirth(new PlaceOfBirth("北京", "中国"));
        hai.setInventions(new String[]{"i1", "i2", "i3"});

        Inventor hainv = new Inventor("海女士", new Date(), "中国");
        hainv.setPlaceOfBirth(new PlaceOfBirth("北京", "中国"));
        hainv.setInventions(new String[]{"in1", "in2", "in3", "in4"});

        Society society = new Society();
        society.setName("码闻创造营");
        society.getMembers().addAll(Arrays.asList(hai, hainv));
        society.getOfficers().put("advisors", Arrays.asList(hai, hainv)); //顾文列表
        society.getOfficers().put("president", hainv); //会长


        //属性，数组，列表，映射和索引器  '点'、中括号
        System.out.println(parser.parseExpression("birthdate.year - 30").getValue(hai, Integer.class));
        //属性名称的首字母允许不区分大小写
        System.out.println(parser.parseExpression("Birthdate.Year - 30").getValue(hai, Integer.class));
        System.out.println(parser.parseExpression("placeOfBirth.city").getValue(hai, String.class));
        //获取数组
        System.out.println(parser.parseExpression("inventions[2]").getValue(hai, String.class));
        //获取数组某个item的某个属性
        System.out.println(parser.parseExpression("members[1].name").getValue(society, String.class));
        //数组的某个item还是数组,这样的话就可以无限的一路“点”下去了
        System.out.println(parser.parseExpression("members[1].Inventions[3]").getValue(society, String.class));
        //与js语言也很像，直接中括号取属性值也可以
        System.out.println(parser.parseExpression("Officers['president']").getValue(society, Inventor.class));
        System.out.println(parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(society, String.class));
        //当然也可以设置值，写操作
        parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(society, "还是中国");
        System.out.println(parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").getValue(society, String.class));


        // 大括号，内联数组和内联映射
        // 大括号可以直接表示数组
        System.out.println(parser.parseExpression("{1,2,3,4}").getValue(List.class));
        // 多维数组也可以
        System.out.println(parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(List.class));
        //与JSON很像，可以定义映射
        System.out.println(parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue());
        //debug一下，看看 obj 是什么类型
        Object obj = parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue();
        //映射可以嵌套，可以使用Map.class, 也可以不使用
        System.out.println(parser.parseExpression("{name:{first:'12',last:'12'},dob:{day:10,month:'1',year:1}}").getValue(Map.class));


        //构建数组，new 操作符
        //定义一维数组
        System.out.println(parser.parseExpression("new int[4]").getValue());
        //初始化
        System.out.println(parser.parseExpression("new int[]{1,2,3}").getValue());
        //多维度数组
        System.out.println(parser.parseExpression("new int[4][5]").getValue());
        //普通类
        System.out.println(parser.parseExpression("new String('hello world').toUpperCase()").getValue());


        //类型判断表达式 instanceof
        //T表示类型，后续会做介绍
        System.out.println(parser.parseExpression("'xyz' instanceof T(Integer)").getValue());

        //可以使用正则表达式 matches
        System.out.println(parser.parseExpression("'100' matches '\\d+' ").getValue());
        System.out.println(parser.parseExpression("'100fghdjf' matches '\\d+'").getValue());
        System.out.println(parser.parseExpression("'valid alphabetic string' matches '[a-zA-Z\\s]+'").getValue());


        //调用方法,很熟悉了，跳过
        System.out.println(parser.parseExpression("new String('hello world').toUpperCase()").getValue());
    }

    //运算符
    public static void two() {
        // 构建模型对象，方便读取
        Inventor hai = new Inventor("海先生", new Date(), "中国");
        hai.setPlaceOfBirth(new PlaceOfBirth("北京", "中国"));
        hai.setInventions(new String[]{"i1", "i2", "i3"});

        ExpressionParser parser = new SpelExpressionParser();
        //运算符，重点
        //关系运算符:	<, >, ==, !=, <=, >=, lt, gt, eq, ne, le, ge  这些
        System.out.println(parser.parseExpression("2 == 2").getValue(Boolean.class));
        System.out.println(parser.parseExpression("2 < -5.0").getValue(Boolean.class));
        // 逻辑运算符 	and, or, not, &&, ||, ! 等等
        System.out.println(parser.parseExpression("true and false").getValue(Boolean.class));
        System.out.println(parser.parseExpression("!true").getValue(Boolean.class));
        // 数学运算符 	+, -, *, /, %, ^, div, mod等等
        System.out.println(parser.parseExpression("8 / 5 % 2").getValue());
        System.out.println(parser.parseExpression("-2 * -3").getValue());
        // 赋值运算符，除了 setValue，也可以使用 getValue 进行赋值
        parser.parseExpression("Name = '这就是码闻'").getValue(hai);
        System.out.println(parser.parseExpression("Name").getValue(hai));
        //三元表达式 内部执行 if-then-else 条件逻辑
        System.out.println(parser.parseExpression("1>2 ? 'hello' : 'world'").getValue());
        //Elvis 运算符  .Elvis 运算符是三元运算符语法的简化，并且在 Groovy/Kotlin/swift等等 语言中使用
        //解释以下语句. // 'Unknown'   ?:
        System.out.println(parser.parseExpression("name?:'Unknown'").getValue(hai, String.class));
        System.out.println(parser.parseExpression("name?:'Unknown'").getValue(new Inventor(), String.class));
        //安全导航运算符
        //安全导航运算符用于避免 NullPointerException
        System.out.println(parser.parseExpression("PlaceOfBirth?.City").getValue(hai, String.class));
        hai.setPlaceOfBirth(null);
        System.out.println(parser.parseExpression("PlaceOfBirth?.City").getValue(hai, String.class));
        System.out.println(parser.parseExpression("PlaceOfBirth.City").getValue(hai, String.class));
    }

    //操作对象和属性
    public static void three() throws NoSuchMethodException {
               // 构建模型对象，方便读取
        Inventor hai = new Inventor("海先生", new Date(), "中国");
        hai.setPlaceOfBirth(new PlaceOfBirth("北京", "中国"));
        hai.setInventions(new String[]{"i1", "i2", "i3"});

        ExpressionParser parser = new SpelExpressionParser();

        //类型 T 字符
        //特殊的 T 运算符来指定 java.lang.Class（类型）的实例,静态方法也可以通过使用此运算符来调用.
        Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
        System.out.println(dateClass);
        Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
        System.out.println(stringClass);
        boolean trueValue = RoundingMode.CEILING.compareTo(RoundingMode.FLOOR) < 0;//四舍五入的值
        System.out.println(trueValue);
        System.out.println(parser.parseExpression("T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR").getValue());

        //构造器 new 字符
        // new 运算符来调用构造函数。注意除了String Int等基础类型，都需要全路径类名
        Inventor i1 = parser.parseExpression(
                "new com.hoioy.clazz.spellearn.model.Inventor('海先生', new java.util.Date(), '中国')")
                .getValue(Inventor.class);
        //以上使用了两个new,打印出结果，或者debug
        System.out.println(i1);


        //变量 #字符
        // #variableName 语法在表达式中引用变量
        // 注意在EvaluationContext中才有效
        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setVariable("a", "1111");
        System.out.println(parser.parseExpression("#a").getValue());
        System.out.println(parser.parseExpression("#a").getValue(context));
        //特殊的是：#this 和 #root 变量
        // #root 执行根上下文对象，#this 只相当前上下文，与JavaScript的this很像
        System.out.println(parser.parseExpression("#root").getValue(hai));
        System.out.println(parser.parseExpression("#this").getValue(hai));
        //this理解
        List<Integer> primes = Arrays.asList(2, 3, 4, 5, 6, 7, 8, 11, 12);
        context.setVariable("primes", primes);
        //此时this指向的是当前遍历list的每一个item
        System.out.println(parser.parseExpression("#primes.?[#this>10]").getValue(context));


        //函数  #运算符
        // 支持自定义函数
        context.setVariable("hello", SpELReference.class.getDeclaredMethod("hello", String.class));
        //自定义函数可以实现字符串与函数的映射，从而实现非常复杂的逻辑
        System.out.println(parser.parseExpression("#hello('海海先生')").getValue(context, String.class));


        //Bean 引用
        context.setBeanResolver(new BeanResolver() {
            public Object resolve(EvaluationContext context, String beanName) throws AccessException {
                if (beanName.equals("something")) {
                    return new Inventor("海女士", new Date(), "中国");
                }
                return null;
            }
        });
        System.out.println(parser.parseExpression("@something.name").getValue(context));

        //Bean引用更多是与BeanFactoryResolver与BeanFactory也就是Spring的IOC容器结合，那么就可以方便地从容器中获取对象了
        //要访问工厂 bean 本身，您应该在 bean 名称前加上＆符号，这个在下一章再演示
    }

    public static String hello(String input) {
        return "Hello:" + input;
    }

    //操作集合
    public static void four(){
        StandardEvaluationContext context = new StandardEvaluationContext();
        //设置一个List
        List<Integer> primes = Arrays.asList(2, 3, 4, 5, 6, 7, 8, 11, 12);
        context.setVariable("primes", primes);
        //设置一个map
        Map map = new HashMap();
        map.put("m1",12);
        map.put("m2",23);
        map.put("m3",30);
        context.setVariable("map", map);

        ExpressionParser parser = new SpelExpressionParser();
        //集合筛选操作，之前遇到过
        //筛选出小于27的元素
        System.out.println(parser.parseExpression("#map.?[value<27]").getValue(context));
        //筛选出小于27的第一个条目
        System.out.println(parser.parseExpression("#map.^[value<27]").getValue(context));
        //筛选出小于27的最后一个条目
        System.out.println(parser.parseExpression("#map.$[value<27]").getValue(context));


        //投影操作,指根据集合中的元素中通过选择来构造另一个集合
        System.out.println(parser.parseExpression("#primes.![#this+1]").getValue(context));
        System.out.println(parser.parseExpression("#map.![value+1]").getValue(context));

        //同时使用集合筛选和投影
        System.out.println(parser.parseExpression("#map.?[key!='m1'].![value+2]").getValue(context, List.class));
    }

    //表达式模板
    public static void five(){
        //在Spring Boot代码中经常见到： @Value("${java_home}")、 @Value("#{ systemProperties}")等等
        //“前缀+表达式+后缀”形式组成，如“${1+2}”即表达式块
        //背后实际就是SpEL的表示模板

        //创建解析器
        SpelExpressionParser parser = new SpelExpressionParser();
        //创建表达式计算上下文
        EvaluationContext evaluationContext = new StandardEvaluationContext();
        evaluationContext.setVariable("name", "海先生");
        evaluationContext.setVariable("lesson", "spring高手系列!");

        //创建 解析器上下文
        ParserContext parserContext = new TemplateParserContext("%{", "}");
        Expression expression = parser.parseExpression("你好:%{#name},我们正在学习:%{#lesson}", parserContext);
        System.out.println(expression.getValue(evaluationContext, String.class));


        //或者 自定义解析器上下文
        expression = parser.parseExpression("你好:!!{#name},我们正在学习:!!{#lesson}"
                , new ParserContext() {
            public boolean isTemplate() {
                return true;
            }

            public String getExpressionPrefix() {
                return "!!{";
            }

            public String getExpressionSuffix() {
                return "}";
            }
        });
        System.out.println(expression.getValue(evaluationContext, String.class));
    }

}
